/*
 *   Copyright 1999-2016 Asiainfo Technologies(China),Inc.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.asiainfo.redis.service;

import com.asiainfo.redis.entity.BusiDataRule;
import com.asiainfo.redis.entity.BusiDatasource;
import com.asiainfo.redis.entity.BusiType;
import com.asiainfo.redis.entity.Cluster;
import com.asiainfo.redis.entity.ClusterBusiTypeRef;
import com.asiainfo.redis.entity.ClusterNode;
import com.asiainfo.redis.entity.Result;
import com.asiainfo.redis.entity.SaveBusiTypesRequest;
import com.asiainfo.redis.repository.BusiDataRuleRepository;
import com.asiainfo.redis.repository.BusiDatasourceRepository;
import com.asiainfo.redis.repository.BusiTypeRepository;
import com.asiainfo.redis.repository.ClusterBusiTypeRefRepository;
import com.asiainfo.redis.repository.ClusterNodeRepository;
import com.asiainfo.redis.repository.ClusterRepository;
import com.asiainfo.redis.service.busidatawriter.BusiDataHandler;
import com.asiainfo.redis.service.busidatawriter.BusiDataRedisWriter4GeneralMultiConditions;
import com.asiainfo.redis.service.busidatawriter.BusiDataRedisWriter4NoCondition;
import com.asiainfo.redis.service.busidatawriter.BusiDataRedisWriter4OneCondition;
import com.asiainfo.redis.service.busidatawriter.BusiDataRedisWriter4ThreeMoreConditionsSaveConditionKeyValues;
import com.asiainfo.redis.service.busidatawriter.BusiDataRedisWriter4ThreeMoreConditionsSaveUniqueKeyValue;
import com.asiainfo.redis.service.busidatawriter.BusiDataRedisWriter4TwoConditions;
import com.asiainfo.redis.web.PageData;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @version v 1.0 on 2016/6/22 10:19
 * @auther william.xu
 */
@Service
public class BusiConfigService {

    protected static final Logger LOGGER = LoggerFactory.getLogger(BusiConfigService.class);

    public static final String SPLIT_CODE = (char)18 + "";//数据库字段SAVE_FIELDS和CONDITION_FIELDS的分隔符

    @Resource
    private BusiTypeRepository busiTypeRepository;

    @Resource
    private BusiDataRuleRepository busiDataRuleRepository;

    @Resource
    private BusiDatasourceRepository busiDatasourceRepository;

    @Resource
    private ClusterRepository clusterRepository;

    @Resource
    private ClusterNodeRepository clusterNodeRepository;

    @Resource
    private ClusterBusiTypeRefRepository clusterBusiTypeRefRepository;

    @Resource
    private RedisService redisService;

    @Resource
    private HostService hostService;

    public List<BusiType> getAllBusiTypeList(){
        return busiTypeRepository.findAll();
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveBusiTypes(SaveBusiTypesRequest request){
        List<BusiType> newList = request.getNewList();
        List<BusiType> updateList = request.getUpdateList();
        for (BusiType busiType : newList){
            busiType.setId(null);
            busiTypeRepository.save(busiType);
        }
        for (BusiType busiType : updateList){
            busiTypeRepository.save(busiType);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void removeBusiType(Long id) throws Exception{
        int count = busiDataRuleRepository.findCountByBusiTypeId(id);
        if (count == 0) {
            busiTypeRepository.delete(id);
        }else{
            throw new Exception("该业务类型正在被使用，不能删除！");
        }
    }

    public List<BusiDatasource> getAllBusiDatasourceList(){
        return busiDatasourceRepository.findAll();
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveBusiDatasource(BusiDatasource busiDatasource) throws Exception{
        String errorMsg = "该数据源(" + busiDatasource.getName() + ")已经存在！";
        try {
            busiDatasourceRepository.save(busiDatasource);
            List<BusiDatasource> list = busiDatasourceRepository.findByName(busiDatasource.getName());
            if (list.size() > 1){
                throw new Exception(errorMsg);
            }
        }catch (DataIntegrityViolationException e){
            throw new Exception(errorMsg);
        }

    }

    @Transactional(rollbackFor = Exception.class)
    public void removeBusiDatasource(Long id) throws Exception{
        int count = busiDataRuleRepository.findCountByBusiDatasourceId(id);
        if (count == 0) {
            busiDatasourceRepository.delete(id);
        }else{
            throw new Exception("该数据源正在被使用，不能删除！");
        }
    }



    public List<Map> getSqlFields(Long datasourceId, String sql) throws Exception{
        List<Map> result = new ArrayList<>();
        Connection connection = null;
        Statement statement = null;
        ResultSet rst = null;
        try {
            connection = getConnection(datasourceId);
            statement = connection.createStatement();
            rst = statement.executeQuery(sql);
            ResultSetMetaData metaData = rst.getMetaData();
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++){
                Map map = new HashMap();
                map.put("fieldName", metaData.getColumnName(i).toUpperCase());
                result.add(map);
            }
        }finally {
            if (connection != null){
                try {
                    connection.close();
                }catch (Exception e){
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                }catch (Exception e){
                }
            }
            if (rst != null) {
                rst.close();
            }
        }
        return result;
    }

    public Result checkDatasource(BusiDatasource busiDatasource){
        Result result = new Result();
        Connection connection = null;
        try {
            connection = getConnection(busiDatasource);
            result.setResult(true);
        }catch (Exception e){
            result.setSuccess(false);
            result.setErrorMsg("测试连接失败：" + e.getMessage());
        }finally {
            if (connection != null){
                try {
                    connection.close();
                }catch (Exception e){
                }
            }
        }
        return result;
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveBusiDataRule(BusiDataRule busiDataRule) throws Exception{
        String errorMsg = "该业务配置(" + busiDataRule.getKeyPrefix() + ")在当前集群的业务系统中已经存在！";
        try {
            //保存业务规则表
            busiDataRuleRepository.save(busiDataRule);
            List list = busiDataRuleRepository.findByClusterIdAndBusiTypeIdAndKeyPrefix(busiDataRule
                    .getClusterId(), busiDataRule.getBusiTypeId(), busiDataRule.getKeyPrefix());
            if (list.size() > 1){
                throw new Exception(errorMsg);
            }

            //保存集群和业务系统关系表(一个集群可以有多个业务系统，用来后台调度，调度粒度在业务系统上)
            ClusterBusiTypeRef clusterBusiTypeRef = clusterBusiTypeRefRepository.findByClusterIdAndBusiTypeId
                    (busiDataRule.getClusterId(), busiDataRule.getBusiTypeId());
            if (clusterBusiTypeRef == null){
                clusterBusiTypeRef = new ClusterBusiTypeRef();
                clusterBusiTypeRef.setClusterId(busiDataRule.getClusterId());
                clusterBusiTypeRef.setBusiTypeId(busiDataRule.getBusiTypeId());
                clusterBusiTypeRefRepository.save(clusterBusiTypeRef);
            }


            //保存规则配置mapping_class和value_type到redis中
            BusiType busiType = busiTypeRepository.findOne(busiDataRule.getBusiTypeId());
            Assert.notNull(busiType, "该业务配置规则对应的busiTypeId不存在");
            String key = "busi_data_rule_config:"+busiType.getBusiCode()+":"+busiDataRule.getKeyPrefix();
            Map value = new HashMap();
            List<Map> data = new ArrayList<>();
            value.put("dataType", "hash");
            value.put("data", data);
            Map map = new HashMap();
            map.put("field", "mapping_class");
            map.put("value", busiDataRule.getMappingClass()==null?"":busiDataRule.getMappingClass());
            data.add(map);
            map = new HashMap();
            map.put("field", "value_type");
            map.put("value", busiDataRule.getValueType()==null?"":busiDataRule.getValueType());
            data.add(map);
            redisService.setValueByKey(key, value, busiDataRule.getClusterId());

        }catch (DataIntegrityViolationException e){
            throw new Exception(errorMsg);
        }
    }

    public BusiDataRule getBusiDataRule(Long busiDataRuleId){
        BusiDataRule busiDataRule = busiDataRuleRepository.findOne(busiDataRuleId);
        Assert.notNull(busiDataRule, "该busiDataRuleId("+busiDataRuleId+")不存在");
        return busiDataRule;
    }

    public Map getBusiDataInfo(Long busiDataRuleId){
        BusiDataRule busiDataRule = getBusiDataRule(busiDataRuleId);

        Cluster cluster = clusterRepository.findOne(busiDataRule.getClusterId());
        Assert.notNull(busiDataRule, "该busiDataRuleId("+busiDataRuleId+")对应的cluster不存在");

        BusiType busiType = busiTypeRepository.findOne(busiDataRule.getBusiTypeId());
        Assert.notNull(busiType, "该busiDataRuleId("+busiDataRuleId+")对应的busiTypeId不存在");

        Map result = new HashMap();
        result.put("configName", busiDataRule.getConfigName());
        result.put("keyPrefix", busiDataRule.getKeyPrefix());
        result.put("clusterName", cluster.getClusterName());
        result.put("busiTypeName", busiType.getBusiName()+"("+busiType.getBusiCode()+")");
        return result;

    }


    public Page<BusiDataRule> getBusiDataRuleList(String configName, String keyPrefix, Long busiTypeId, Long clusterId,
                                                  Pageable pageable) {
        if (configName == null) {
            configName = "";
        }
        if (keyPrefix == null) {
            keyPrefix = "";
        }
        return busiDataRuleRepository.findByConfigNameAndkeyPrefixAndBusiTypeId(configName, keyPrefix, busiTypeId,
                clusterId, pageable);
    }

    public String getSaveFields(Long busiDataRuleId){
        BusiDataRule busiDataRule = getBusiDataRule(busiDataRuleId);
        return busiDataRule.getSaveFields();
    }

    public PageData<Map> getDatabasePageData(Long busiDataRuleId, String sqlCondition, Pageable pageable) throws
            Exception {
        BusiDataRule busiDataRule = getBusiDataRule(busiDataRuleId);
        BusiDataHandler busiDataHandler = new BusiDataHandler(busiDataRule);
        String sql = "select * from (" + busiDataRule.getSql() + ") where ";
        if (sqlCondition == null || "".equals(sqlCondition)){
            sqlCondition = "1=1";
        }
        sql += sqlCondition;
        return busiDataHandler.getDatabasePageData(sql, pageable);
    }


    public List<Map> getRedisKeys(Long busiDataRuleId, Map data) throws Exception{
        BusiDataRule busiDataRule = getBusiDataRule(busiDataRuleId);

        String busiTypeCode = busiTypeRepository.findOne(busiDataRule.getBusiTypeId()).getBusiCode();
        String keyPrefix = busiDataRule.getKeyPrefix();

        List<Map> keys = new ArrayList<>();
        Integer conditionType = busiDataRule.getConditionType();
        switch (conditionType){
            case 0:
                keys = redisService.getkeysByKeyQuery(busiTypeCode+":"+keyPrefix, busiDataRule.getClusterId());
                break;
            case 1:
            case 2:
                String conditionField = busiDataRule.getConditionField1();
                Object value = data.get(conditionField);
                String conditionFieldValue = (value==null?"":value.toString());
                keys = redisService.getkeysByKeyQuery(busiTypeCode+":"+keyPrefix+":"+conditionFieldValue, busiDataRule.getClusterId());
                break;
            case 3:
                String[] conditionFields = busiDataRule.getConditionFields().split(SPLIT_CODE);
                for (int i = 0; i < conditionFields.length; i++){
                    String conditionFieldName = conditionFields[i];
                    value = data.get(conditionFieldName);
                    conditionFieldValue = (value==null?"":value.toString());
                    keys.addAll(redisService.getkeysByKeyQuery(busiTypeCode+":"+keyPrefix+":"+conditionFieldName+":"+conditionFieldValue,
                            busiDataRule.getClusterId()));
                }
                break;
            case 4:
                conditionFields = busiDataRule.getConditionFields().split(SPLIT_CODE);
                String[] conditionFieldValues = new String[conditionFields.length];
                for (int i = 0; i < conditionFields.length; i++){
                    value = data.get(conditionFields[i]);
                    conditionFieldValues[i] = (value==null?"":value.toString());
                }
                keys = redisService.getkeysByKeyQuery(busiTypeCode+":"+keyPrefix+":"+StringUtils.join(conditionFieldValues, ":"), busiDataRule.getClusterId());

        }
        return keys;
    }

    public Map getRedisValueByKey(Long busiDataRuleId, String key) throws Exception{
        BusiDataRule busiDataRule = getBusiDataRule(busiDataRuleId);
        return redisService.getValueByKey(key, busiDataRule.getClusterId());
    }

    public void setRedisValueByKey(Long busiDataRuleId, String key, Map value) throws Exception{
        BusiDataRule busiDataRule = getBusiDataRule(busiDataRuleId);

        redisService.setValueByKey(key, value, busiDataRule.getClusterId());
    }

    /**
     * 增量更新写入redis或从redis删除，主用和备用集群都要刷新
     * @param busiDataRuleId
     */
    public void writeDataToRedis(Long busiDataRuleId, String sqlCondition, Integer operFlag)throws Exception{
        BusiDataRule busiDataRule = getBusiDataRule(busiDataRuleId);

        Boolean isAllUpdate = false;
        String sql = "select * from (" + busiDataRule.getSql() + ") where ";
        if (sqlCondition == null || "".equals(sqlCondition)){
            sqlCondition = "1=1";
            isAllUpdate = true;
        }
        sql += sqlCondition;

        Long clusterId = busiDataRule.getClusterId();
        //先增量刷新主用集群
        writeDataToRedis(busiDataRule, sql, isAllUpdate, operFlag, clusterId);
        Long standbyClusterId = redisService.getStandbyClusterId(clusterId);
        if (standbyClusterId != null) {
            //存在备用集群，则增量刷新备用集群
            writeDataToRedis(busiDataRule, sql, isAllUpdate, operFlag, standbyClusterId);
        }else{
            LOGGER.warn("该业务配置规则对应的集群没有配置备用集群,clusterId=" + clusterId + ",busiDataRuleId=" + busiDataRule.getId());
        }

    }

    public void removeAllRedisData(BusiDataRule busiDataRule, Long clusterId) throws Exception{
        BusiDataHandler busiDataHandler = new BusiDataHandler(busiDataRule);
        busiDataHandler.removeAllRedisData(clusterId);
    }

    //因为删除redis数据很慢，所以直接把redis停掉，删除数据文件，重启
    public void removeAllRedisData(Long clusterId) throws Exception{
        List<ClusterNode> list = clusterNodeRepository.findByClusterId(clusterId);
        for (ClusterNode clusterNode : list){
            hostService.resetRedisClusterNode(clusterNode);
        }
        if (!redisService.checkRedisClusterStatus(clusterId)){
            throw new Exception("删除数据文件，重启redis集群后，集群处于不正常状态");
        }
    }

    public void writeDataToRedis(BusiDataRule busiDataRule, String sql, Boolean isAllUpdate, Integer operFlag, Long clusterId) throws Exception{
        LOGGER.info("正在写业务数据id="+busiDataRule.getId()+",key_prefix="+busiDataRule.getKeyPrefix()
                + ",name="+busiDataRule.getConfigName()+"到集群clusterId="+clusterId);
        BusiDataHandler busiDataHandler = new BusiDataHandler(busiDataRule);

        Integer conditionType = busiDataRule.getConditionType();
        switch (conditionType){
            case 0:
                busiDataHandler.queryDataAndWriteToRedis(isAllUpdate, sql, clusterId,
                        new BusiDataRedisWriter4NoCondition(operFlag));
                break;
            case 1:
                sql += (" order by " + busiDataRule.getConditionField1());
                busiDataHandler.queryDataAndWriteToRedis(isAllUpdate, sql, clusterId,
                        new BusiDataRedisWriter4OneCondition(operFlag));
                break;
            case 2:
                sql += (" order by " + busiDataRule.getConditionField1() + "," + busiDataRule.getConditionField1());
                busiDataHandler.queryDataAndWriteToRedis(isAllUpdate, sql, clusterId,
                        new BusiDataRedisWriter4TwoConditions(operFlag));
                break;
            case 3:
                busiDataHandler.queryDataAndWriteToRedis(isAllUpdate, sql, clusterId,
                        new BusiDataRedisWriter4ThreeMoreConditionsSaveUniqueKeyValue(operFlag));
                String[] conditionFields = busiDataRule.getConditionFields().split(SPLIT_CODE);
                for (String conditionField : conditionFields){
                    busiDataHandler.queryDataAndWriteToRedis(false, sql + (" order by " + conditionField), clusterId,
                            new BusiDataRedisWriter4ThreeMoreConditionsSaveConditionKeyValues(operFlag, conditionField));
                }
                break;
            case 4:
                conditionFields = busiDataRule.getConditionFields().split(SPLIT_CODE);
                sql += (" order by " + StringUtils.join(conditionFields, ","));
                busiDataHandler.queryDataAndWriteToRedis(isAllUpdate, sql, clusterId,
                        new BusiDataRedisWriter4GeneralMultiConditions(operFlag));
                break;
            default:
                break;
        }

    }

    public Connection getConnection(Long datasourceId) throws Exception{
        BusiDatasource busiDatasource = busiDatasourceRepository.findOne(datasourceId);
        return getConnection(busiDatasource);
    }

    private Connection getConnection(BusiDatasource busiDatasource) throws Exception{
        if (busiDatasource == null){
            throw new Exception("该数据源不存在，请选择可用的数据源！");
        }
        Class.forName(busiDatasource.getDriverClass());
        return DriverManager.getConnection(busiDatasource.getJdbcUrl(),busiDatasource.getUserName(),
                busiDatasource.getPassword());
    }


}
